package core.util;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import core.config.Factory;

/**
 * 数値ユーティリティ
 * @author Tadashi Nakayama
 */
public final class NumberUtil {

	/** 数字判断パターン */
	private static final Pattern PATTERN_NUMBER = Pattern.compile("^[\\-0-9 ,\\.]+$");

	/**
	 * コンストラクタ
	 */
	private NumberUtil() {
		throw new AssertionError();
	}

	/**
	 * 同値判断
	 * @param val1 値1
	 * @param val2 値2
	 * @return 同値の場合 true を返す。
	 */
	public static boolean compare(final BigDecimal val1, final BigDecimal val2) {
		return val1 == val2 || (val1 != null && val2 != null && val1.compareTo(val2) == 0);
	}

	/**
	 * 文字列から数字のみ抽出
	 * @param val 文字列
	 * @return 数字文字列
	 */
	public static String pickup(final String val) {
		String value = (val == null) ? "" : val;
		StringBuilder sb = value.codePoints().filter(cp -> '0' <= cp && cp <= '9').collect(
						StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append);
		return sb.toString();
	}

	/**
	 * 連番配列取得
	 *
	 * @param length 長さ
	 * @return 連番配列
	 */
	public static Integer[] sequence(final int length) {
		return IntStream.range(0, length).mapToObj(Integer::valueOf).toArray(Integer[]::new);
	}

	/**
	 * 加算
	 * @param vals Integer配列
	 * @return 合計
	 */
	public static Integer add(final Integer... vals) {
		Optional<Integer> opt = Stream.of(vals != null ? vals : new Integer[0]).
						filter(Objects::nonNull).reduce(Integer::sum);
		return opt.orElse(null);
	}

	/**
	 * 加算
	 * @param vals Long配列
	 * @return 合計
	 */
	public static Long add(final Long... vals) {
		Optional<Long> opt = Stream.of(vals != null ? vals : new Long[0]).
						filter(Objects::nonNull).reduce(Long::sum);
		return opt.orElse(null);
	}

	/**
	 * 加算
	 * @param vals BigDecimal配列
	 * @return 合計
	 */
	public static BigDecimal add(final BigDecimal... vals) {
		Optional<BigDecimal> opt = Stream.of(vals != null ? vals : new BigDecimal[0]).
						filter(Objects::nonNull).reduce(BigDecimal::add);
		return opt.orElse(null);
	}

	/**
	 * 乗算
	 * @param vals BigDecimal配列
	 * @return 乗算結果
	 */
	public static BigDecimal multiply(final BigDecimal... vals) {
		BigDecimal ret = null;
		BigDecimal base = null;
		if (vals != null) {
			for (final BigDecimal val : vals) {
				if (val != null) {
					if (base == null) {
						base = val;
					} else if (ret == null) {
						ret = base.multiply(val);
					} else {
						ret = ret.multiply(val);
					}
				}
			}
		}
		return ret;
	}

	/**
	 * 減算
	 * @param vals BigDecimal配列
	 * @return 減算結果
	 */
	public static BigDecimal subtract(final BigDecimal... vals) {
		BigDecimal ret = null;
		if (vals != null && 0 < vals.length) {
			ret = vals[0];
			for (int i = 1; ret != null && i < vals.length; i++) {
				if (vals[i] != null) {
					ret = ret.subtract(vals[i]);
				}
			}
		}
		return ret;
	}

	/**
	 * スケール設定
	 * @param val 値
	 * @param scale スケール
	 * @param mode モード
	 * @return BigDecimal
	 */
	public static BigDecimal setScale(final BigDecimal val,
					final int scale, final RoundingMode mode) {
		return val != null ? val.setScale(scale, mode) : val;
	}

	/**
	 * 相違判断
	 * @param vals BigDecimal配列
	 * @return 相違がある場合 true を返す。
	 */
	public static boolean different(final BigDecimal... vals) {
		for (int i = 0; vals != null && i < vals.length - 1; i++) {
			if (vals[i] != vals[i + 1]
							&& (vals[i] == null || vals[i + 1] == null
							|| vals[i].compareTo(vals[i + 1]) != 0)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Long化
	 * @param val 文字列
	 * @return Long
	 */
	public static Long toLong(final String val) {
		return toLong(val, null);
	}

	/**
	 * Long化
	 * @param val 文字列
	 * @param def デフォルト値
	 * @return Long
	 */
	public static Long toLong(final String val, final Long def) {
		if (!Objects.toString(val, "").isEmpty()) {
			try {
				return Long.valueOf(val.replaceAll("[, ]", ""));
			} catch (final NumberFormatException ex) {
				return def;
			}
		}
		return def;
	}

	/**
	 * Long値化
	 * @param num Number
	 * @return Long値
	 */
	public static Long toLong(final Number num) {
		return toLong(num, null);
	}

	/**
	 * Long化
	 * @param num Number
	 * @param def デフォルト値
	 * @return Long
	 */
	public static Long toLong(final Number num, final Long def) {
		if (Long.class.isInstance(num)) {
			return Long.class.cast(num);
		}
		return num != null ? Long.valueOf(num.longValue()) : def;
	}

	/**
	 * int化
	 * @param val 文字列
	 * @param def デフォルト値
	 * @return int
	 */
	public static int toInt(final String val, final int def) {
		return toInteger(val, Integer.valueOf(def)).intValue();
	}

	/**
	 * Integer化
	 * @param val 文字列
	 * @return Integer
	 */
	public static Integer toInteger(final String val) {
		return toInteger(val, null);
	}

	/**
	 * Integer化
	 * @param val 文字列
	 * @param def デフォルト値
	 * @return Integer
	 */
	public static Integer toInteger(final String val, final Integer def) {
		if (!Objects.toString(val, "").isEmpty()) {
			try {
				return Integer.valueOf(val.replaceAll("[, ]", ""));
			} catch (final NumberFormatException ex) {
				return def;
			}
		}
		return def;
	}

	/**
	 * Integer値化
	 * @param num Number
	 * @return Integer値
	 */
	public static Integer toInteger(final Number num) {
		return toInteger(num, null);
	}

	/**
	 * Integer値化
	 * @param num Number
	 * @param def デフォルト値
	 * @return Integer値
	 */
	public static Integer toInteger(final Number num, final Integer def) {
		if (Integer.class.isInstance(num)) {
			return Integer.class.cast(num);
		}
		return num != null ? Integer.valueOf(num.intValue()) : def;
	}

	/**
	 * BigDecimal化（全角数字等は変換しない。）
	 * @param val 文字列
	 * @return BigDecimal
	 */
	public static BigDecimal toBigDecimal(final String val) {
		return toBigDecimal(val, null);
	}

	/**
	 * BigDecimal化（全角数字等は変換しない。）
	 * @param val 文字列
	 * @param def デフォルト値
	 * @return BigDecimal
	 */
	public static BigDecimal toBigDecimal(final String val, final BigDecimal def) {
		if (val != null && PATTERN_NUMBER.matcher(val).matches()) {
			try {
				return new BigDecimal(val.replaceAll("[, ]", ""));
			} catch (final NumberFormatException ex) {
				return def;
			}
		}
		return def;
	}

	/**
	 * BigDecimal値化
	 * @param num Number
	 * @return Integer値
	 */
	public static BigDecimal toBigDecimal(final Number num) {
		return toBigDecimal(num, null);
	}

	/**
	 * BigDecimal値化
	 * @param num Number
	 * @param def デフォルト値
	 * @return Integer値
	 */
	public static BigDecimal toBigDecimal(final Number num, final BigDecimal def) {
		if (BigDecimal.class.isInstance(num)) {
			return BigDecimal.class.cast(num);
		}
		return num != null ? new BigDecimal(num.toString()) : def;
	}

	/**
	 * Numberオブジェクト化
	 * @param <T> Numberジェネリックス
	 * @param val 文字列
	 * @param cls Numberクラス
	 * @return Numberオブジェクト
	 */
	public static <T extends Number> T toNumber(final String val, final Class<T> cls) {
		if (val != null) {
			try {
				return Factory.construct(
						Factory.getConstructor(cls, String.class), val.replaceAll("[, ]", ""));
			} catch (final NumberFormatException ex) {
				return null;
			}
		}
		return null;
	}
}
